(* 3D Engine object. Do a 3D rendering of a 2D map with a Wavefront/OBJ file
 * AUTHOR: HUGO BRAC
 * *) 
class glEngine glgtk_area =
object (s)
  val area : GlGtk.area = glgtk_area
  
  val mutable glList_fill = None
  val mutable glList_wire = None
  val mutable glList_WF = None
  val mutable vertex3Tab = Array.make 0 (0.,0.,0.)
  val mutable faceList = []
  val mutable cam = new Cam.cam ()
  val mutable fill_mode = false
  val mutable lined = false
  val mutable objPath = ""
  val mutable decal = 10.
  val mutable xmouse = 0.
  val mutable ymouse = 0.

  val mutable i = 0.
  val mutable minX = 0.
  val mutable minY = 0.
  val mutable minZ = 0.
  val mutable maxX = 1.
  val mutable maxY = 1.
  val mutable maxZ = 1.

  method init () =
    vertex3Tab <- Array.make 0 (0.,0.,0.);
    faceList <- [];

    GlDraw.shade_model `smooth; (* line Aliasing *)
    GlFunc.depth_func `less;
    GlDraw.point_size 32.;
    GlDraw.line_width 1.;
    List.iter Gl.enable [`lighting;
                         `light0;
                         `light1;
                         `depth_test;
                         `color_material;
                         `polygon_offset_line]


   
  method initList () =
    glList_fill <- Some (GlList.create `compile);
    s#drawPolygons `polygon faceList;
    GlList.ends ();
    glList_wire <- Some (GlList.create `compile);
    s#drawPolygons `line_loop faceList;
    GlList.ends ();
    glList_WF <- Some (GlList.create `compile);
    s#drawPolygons ~d:0.1 ~color:false `line_loop faceList;
    GlList.ends ();

  method initLights () =
    GlMat.mode `modelview;
    GlMat.push ();

    GlMat.translate 
      ~z:(maxZ +. minZ +. 10.)
      ~x:((maxX +. minX) /. 2.)
      ~y:((maxY +. minY) /. 2.) ();

    let amb = 0.8, 0.8, 0.8, 0.0 and
        dif = 0.8, 0.8, 0.8, 1.0 and
        spec = 0., 0., 0., 0.0 and
        pos = 1.0, 1.0, 1.0, 1.0 in
      List.iter (GlLight.light ~num:0) [`ambient amb;
                                        `diffuse dif;
                                        `specular spec;
                                        `position pos;];
    GlMat.pop()

  method secondLight () =
    let diff = 0.3, 0.3, 0.3, 1.0 in
      List.iter (GlLight.light ~num:1) [`diffuse diff]

  method addVertex3 _ x y z =
    let x = -1. *. x in
      if x > maxX then maxX <- x;
      if y > maxY then maxY <- y;
      if z > maxZ then maxZ <- z;
      if x < minX then minX <- x;
      if y < minY then minY <- y;
      if z < minZ then minZ <- z;
  vertex3Tab <- Array.append vertex3Tab [|(x,y,z)|]
  
  method addFace a =
    faceList <- a::faceList

  method addFaceStr l =
    let tmp = ref 0 in
    let buffer = ref [] in
    let i = ref 0 in
      tmp := 0;
      buffer := [];
      i := 0;
      while !i < (String.length l)  do
        if l.[!i] = ' ' || l.[!i] = '\r' || l.[!i] = '\n' then
          begin
            if (!tmp) > 0 then
              begin
                buffer := (!tmp)::(!buffer);
                tmp := 0;
              end
          end
        else
          begin
            tmp := (!tmp) * 10 + (int_of_char l.[!i] - int_of_char '0');
          end;
        i := !i+1;
      done;
      if !buffer = [] then
         ()
      else
        s#addFace !buffer
  
  method loadObj objPath =
    if Sys.file_exists objPath then
      begin
        let file_in = open_in objPath in
          try
            while true do
              let line = input_line file_in in
                match line.[0] with
                  |'v' -> Scanf.bscanf (Scanf.Scanning.from_string line)
                      "%c %f %f %f" (s#addVertex3)
                  |'f' -> s#addFaceStr
                      (String.sub line 2 (String.length line - 2))
                  | _ -> ()
            done;
          with _ ->
            close_in_noerr file_in;
            cam <- new Cam.cam () ~maxX:maxX ~maxY:maxY ~maxZ:maxZ
                       ~minX:minX ~minY:minY ~minZ:minZ;
            cam#lookFromTop ()
      end

  method color z =
    let rec fillColor = function
      |[] -> GlDraw.color (1.0, 1.0, 1.0)
      |((r,g,b),h)::l  when h +. decal >= z -> GlDraw.color ((float_of_int)r /. 255.,(float_of_int) g /. 255.,(float_of_int)b /. 255.)
      | (_)::l -> fillColor l in 
      fillColor !ObjCons.heights_list;

  method reshape ~width:w ~height:h =
    cam#reshape ~width:w ~height:h

  method display () =
    GlClear.clear [`color; `depth];
    GlMat.mode `projection;
    GlMat.push ();
    GlMat.mode `modelview;
    GlMat.push ();

    s#initLights ();
    GlLight.material ~face:`front (`specular (0.0,1.0,0.0,1.0));
    GlLight.material ~face:`front (`shininess 97.0);
    cam#loop ();
    s#callDispLists() ;
    GlMat.mode `projection;
    GlMat.pop ();
    GlMat.mode `modelview;
    GlMat.pop ();
    Gl.flush ();
    area#swap_buffers ()

  method callDispLists () =
    let f = function
      | None -> ()
      | Some l -> GlList.call l in
    if fill_mode then
      f glList_fill
    else
      f glList_wire;

    if (lined && fill_mode) then
      f glList_WF

  method drawPolygons ?(d=0.) ?(color=true) caps = function
      | [] -> ()
      | e::l ->
          begin
            GlDraw.begins caps;
            List.iter
             (fun i -> 
             let (x,y,z) = vertex3Tab.(i - 1) in
             if color then
               s#color z
             else
               GlDraw.color(0.,0.,0.);
             GlDraw.vertex3 (x,y,z+.d))
             e;
            GlDraw.ends ();
            s#drawPolygons ~d:d ~color:color caps l;
          end

  method motionEvent x y =
    let myX = xmouse -. x in
    let myY = ymouse -. y in
      if myX < 20. && myY < 20. && myX > -20. && myY > -20. then
        begin
          cam#freeFlyCameraAngle (myX) (myY);
        end;
      xmouse <- x;
      ymouse <- y

  method kbEvent (k:int) =
    cam#keyboardEvent k

  method getCamPos () = cam#getPos ()

  method setFreeFly b = cam#setFreeFly b

  method setLine b =
    lined <- b

  method setFillMode b = fill_mode <- b
  method getFillMode = fill_mode

  method setGl b cap () =
    match (b) with
    | true -> Gl.enable cap
    | false -> Gl.disable cap
end
